﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using DriveBeamEditor.Controls.BeamNG.VehiclePartConfig;
using DriveBeamEditor.Controls.General;
using JBeamLibrary;
using JBeamLibrary.Tools;
using JBeamLibrary.Types;
using JBeamLibrary.VehicleParts.Elements;
using JBeamLibrary.VehicleParts.Enums;

namespace DriveBeamEditor.Controls.BeamNG.SectionControls
{
    public partial class CamerasControl : VehiclePartControl
    {
        #region Properties

        public InternalCamera CurrentCamera
        {
            get { return internalCamerasSource.Current as InternalCamera; }
        }

        private ElementPropertyUse PropertyUse
        {
            get { return rbGroupProp.Checked ? ElementPropertyUse.Group : ElementPropertyUse.Single; }
        }

        #endregion

        #region CTORs

        public CamerasControl()
        {
            InitializeComponent();
        }

        #endregion

        #region Events

        #region Binding source

        private void internalCamerasSource_AddingNew(object sender, AddingNewEventArgs e)
        {
            if (DesignMode || VehiclePart == null)
            {
                e.NewObject = new InternalCamera();
                return;
            }

            /* Avoid loop */
            if (e.NewObject is InternalCamera) return;

            /* Not enough nodes, do not allow */
            if (VehiclePart.Nodes.Count < 6)
            {
                MessageBox.Show(this,
                    @"The selected vehicle part doesn't have enough nodes to create an internal camera. It requires a minimum of 6 nodes.",
                    @"Not enough nodes", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                internalCamerasSource.CancelEdit();
                grid.NotifyCurrentCellDirty(true);
                grid.CancelEdit();
                grid.NotifyCurrentCellDirty(false);
                return;
            }

            /* Create an internal camera */
            e.NewObject = new InternalCamera(
                Float3.Origin,
                65f,
                VehiclePart.Nodes[0].Id,
                VehiclePart.Nodes[1].Id,
                VehiclePart.Nodes[2].Id,
                VehiclePart.Nodes[3].Id,
                VehiclePart.Nodes[4].Id,
                VehiclePart.Nodes[5].Id
            );
        }

        private void internalCamerasSource_CurrentChanged(object sender, EventArgs e)
        {
            /* No vehicle part loaded */
            if (VehiclePart == null) return;

            /* Update property box title */
            boxInternalProperties.Text =
                internalCamerasSource.Position >= 0 && VehiclePart.InternalCameraSection.Count > 0
                ? string.Format("Camera '{0}' properties", VehiclePart.InternalCameraSection[internalCamerasSource.Position])
                : @"Camera properties";

            /* Display properties */
            UpdateProperties(internalCamerasSource.Position);
        }

        #endregion

        #region Data grid view

        private void grid_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            /* Clicks on headers */
            if (e.ColumnIndex < 0 || e.RowIndex < 0) return;

            /* Get column & row & cell */
            var col = grid.Columns[e.ColumnIndex];
            var row = grid.Rows[e.RowIndex];

            /* Change offset */
            if (!row.IsNewRow && col.DataPropertyName.Equals("Offset"))
                ppdOffset.Show(grid, grid.PointToClient(Cursor.Position));

            /* Select a node for the current cell */
            else if (col.DataPropertyName.StartsWith("NodeId"))
            {
                if (Config.SelectNodesFromFromCurrentPartOnly)
                    pddNodeSelection.Show(grid, grid.PointToClient(Cursor.Position));
                else
                    pddNodeSelectionFull.Show(grid, grid.PointToClient(Cursor.Position));
            }
        }

        private void grid_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            /* Clicks on headers */
            if (e.ColumnIndex < 0 || e.RowIndex < 0) return;

            /* Get column & row & cell */
            var col = grid.Columns[e.ColumnIndex];
            var row = grid.Rows[e.RowIndex];

            /* Validate new row */
            if (row.IsNewRow && col == columnDelete)
            {
                internalCamerasSource.EndEdit();
                grid.NotifyCurrentCellDirty(true);
                grid.EndEdit();
                grid.NotifyCurrentCellDirty(false);
            }

            /* Offset */
            else if (col.DataPropertyName.Equals("Offset"))
            {
                ppdOffset.Show(grid, grid.PointToClient(Cursor.Position));
            }

            /* Select a node for the current cell */
            else if (col.DataPropertyName.StartsWith("NodeId"))
            {
                if (Config.SelectNodesFromFromCurrentPartOnly)
                    pddNodeSelection.Show(grid, grid.PointToClient(Cursor.Position));
                else
                    pddNodeSelectionFull.Show(grid, grid.PointToClient(Cursor.Position));
            }

            /* Delete the current row */
            else if (col == columnDelete)
            {
                grid.Rows.RemoveAt(e.RowIndex);
            }
        }

        #endregion

        #region Popups

        private void popupNodeSelection_SelectedNodeChanged(object sender, Node e)
        {
            if (grid.CurrentRow == null || grid.CurrentCell == null || e == null) return;

            var cam = grid.CurrentRow.DataBoundItem as InternalCamera;
            if (cam == null) return;

            var col = grid.Columns[grid.CurrentCell.ColumnIndex];
            if (col == null) return;

            if (col.DataPropertyName.Equals("NodeId1"))
                cam.NodeId1 = e.Id;
            else if (col.DataPropertyName.Equals("NodeId2"))
                cam.NodeId2 = e.Id;
            else if (col.DataPropertyName.Equals("NodeId3"))
                cam.NodeId3 = e.Id;
            else if (col.DataPropertyName.Equals("NodeId4"))
                cam.NodeId4 = e.Id;
            else if (col.DataPropertyName.Equals("NodeId5"))
                cam.NodeId5 = e.Id;
            else if (col.DataPropertyName.Equals("NodeId6"))
                cam.NodeId6 = e.Id;
        }

        private void ppdOffset_Opening(object sender, CancelEventArgs e)
        {
            if (CurrentCamera != null)
                popupOffset.SelectedValue = CurrentCamera.Offset;
        }

        private void popupOffset_SelectedValueChanged(object sender, Float3 e)
        {
            if (grid.CurrentRow == null || grid.CurrentCell == null) return;

            var cam = grid.CurrentRow.DataBoundItem as InternalCamera;
            if (cam == null) return;

            var col = grid.Columns[grid.CurrentCell.ColumnIndex];
            if (col == null) return;

            if (col.DataPropertyName.Equals("Offset"))
                cam.Offset = e;
        }

        #endregion

        #region Property controls

        private void kvcEditor_EditValueChanged(object sender, EventArgs e)
        {
            var editor = sender as KeyValueControl;
            if (editor == null) return;

            /* Set the property value */
            var nodeProperties = VehiclePart.InternalCameraSection.Properties;
            var index = internalCamerasSource.Position;
            nodeProperties.SetPropertyValue(index, editor.Key, PropertyUse, editor.EditValue);
        }

        private void kvcExternal_EditValueChanged(object sender, EventArgs e)
        {
            var editor = sender as KeyValueControl;
            if (editor == null) return;

            /* Update value */
            switch (editor.Key)
            {
                case "Distance": VehiclePart.CameraExternal.Distance = editor.EditValue.To<float>(); break;
                case "DistanceMin": VehiclePart.CameraExternal.DistanceMin = editor.EditValue.To<float>(); break;
                case "FOV": VehiclePart.CameraExternal.FOV = editor.EditValue.To<float>(); break;
                case "Offset": VehiclePart.CameraExternal.Offset = editor.EditValue.To<Float3>(); break;
            }
        }

        #endregion

        #region Radio buttons

        private void rbGroupProp_CheckedChanged(object sender, EventArgs e)
        {
            if (rbGroupProp.Checked)
                OnPropertyUseChanged();
        }

        private void rbInlineProp_CheckedChanged(object sender, EventArgs e)
        {
            if (rbInlineProp.Checked)
                OnPropertyUseChanged();
        }

        #endregion

        #endregion

        #region Methods

        public override string ToString()
        {
            return "Cameras";
        }

        private void BindExternalCamera()
        {
            if (VehiclePart == null) return;

            var camera = VehiclePart.CameraExternal;
            if (camera == null) return;

            kvcDistance.SetEditValueSilent(camera.Distance);
            kvcDistanceMin.SetEditValueSilent(camera.DistanceMin);
            kvcFOV.SetEditValueSilent(camera.FOV);
            kvcOffset.SetEditValueSilent(camera.Offset);
        }

        private void BindInternalCameras()
        {
            grid.Enabled = false;
            internalCamerasSource.DataSource = null;

            if (VehiclePart != null)
                internalCamerasSource.DataSource = VehiclePart.InternalCameraSection.ElementCollection;

            grid.Refresh();
            grid.Enabled = true;
        }

        protected override void OnVehiclePartChanged()
        {
            popupNodeSelection.VehiclePart = VehiclePart;
            pcNodeSelectionFull.Vehicle = VehiclePart != null ? VehiclePart.File.Parent : null;
            BindExternalCamera();
            BindInternalCameras();
            base.OnVehiclePartChanged();

            externalCameraSource.DataSource = VehiclePartSource;
        }

        private void OnPropertyUseChanged()
        {
            UpdateProperties(internalCamerasSource.Position);
        }

        private void UpdateProperties(int index)
        {
            foreach (var editor in
                from editor in boxInternalProperties.Controls.OfType<KeyValueControl>()
                select editor
            )
            {
                var val = GroupHelper.GetPropertyValue(VehiclePart.InternalCameraSection, index, editor.Key, PropertyUse);
                editor.SetEditValueSilent(val);
            }
        }

        #endregion
    }
}
